Skip to content

Generalize recipes and implement them#1449

Closed
Limeth wants to merge 37 commits intoSpongePowered:bleedingfrom
Limeth:feature/recipe-ii
Closed

Generalize recipes and implement them#1449
Limeth wants to merge 37 commits intoSpongePowered:bleedingfrom
Limeth:feature/recipe-ii

Conversation

@Limeth
Copy link
Contributor

@Limeth Limeth commented Jan 2, 2017

SpongeAPI|SpongeCommon|SpongeForge

This is a WIP continuation of kashike's PR, with his consent.
It aims to generalize how recipes are registered and handled. Instead of checking the equality of the ingredients, it works by testing ingredient predicates. It also aims to let the user implement the recipe interfaces by themselves, making it, for example, possible to transfer NBT data from the ingredients to the result.
In this PR, ItemStackSnapshots are used extensively. This is to prevent the user from modifying the original ItemStack. I could instead provide an ItemStack#copy, but that doesn't make it visually obvious the original item won't change.

@Limeth
Copy link
Contributor Author

Limeth commented Jan 2, 2017

The SpongeAPI part is now in the state of what I'd like to propose. Comments and reviews very welcome. Continuing by attempting to implement the SpongeCommon part.

* @throws IllegalArgumentException If the aisle does not contain
* the specified character symbol
*/
Builder where(char symbol, @Nullable ItemStackSnapshot ingredient) throws IllegalArgumentException;
Copy link
Member

@ST-DDT ST-DDT Jan 2, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • I guess this can be default implemented as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It cannot, the default item matching behavior in vanilla Minecraft is defined in NMS code and I needed to mimic it. Can't do that with SpongeAPI. Maybe I should create a method in SpongeCommon that mimics the behavior and expose it via SpongeAPI?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't accept null as ingredient. Ask users to use ItemStackSnapshot.NONE

Copy link
Member

@gabizou gabizou Jan 10, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Limeth What part of item matching is not possible in SpongeAPI?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gabizou This part, as far as I'm aware.

Copy link
Contributor

@Aaron1011 Aaron1011 Feb 10, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Limeth: That link seems to be broken. Can the implementation take care of passing in ItemStackSnapshot.NONE to the predicate, if that's what the issue is?

/**
* @return {@code width * height} for shaped recipes, or the number of ingredients for shapeless recipes
*/
int getRecipeSize();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When would i/the impl need this method?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The implementation on the NMS side assigns priority to recipes depending on this value. You would use this to change that priority.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please document this behaviour in the javadocs.

* Removes the given CraftingRecipe from registration in this registry.
*
* @param recipe The Recipe to unregister
* @param recipe The CraftingRecipe to unregister
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The javadocs mention crafting recipes but the generic type allows plain recipes as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, didn't catch this one after auto-refactoring it.

* @return An {@link ItemStack} or {@link Optional#empty()} if the given
* {@link GridInventory} does not match this recipe's requirements.
*/
Optional<ItemStack> getResult(GridInventory grid);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here you get the results but how do you actually consume the items? Is it required to reduce the number by one? How about Blood Magic's magicians orbs that act as a mere catalyst and thus is not consumed? Or recipes where a tool is used and the tool only takes some damage?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The removal of items from the crafting grid is handled by the CraftingManager in NMS. To preserve items in the crafting grid, provide them via #getRemainingItems(GridInventory).

* @return Whether this {@param ingredient} can be used to craft the result
*/
default boolean isValid(ItemStackSnapshot ingredient) {
// ItemStackSnapshot#NONE gets replaced at runtime
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing null check.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The argument is non-null by default, as defined in the package-info.java file in the package.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, turns out @Nonnull is only used for static analysis.


@Override
default boolean isValid(GridInventory grid, World world) {
List<Predicate<ItemStackSnapshot>> predicates = Lists.newLinkedList(getIngredientPredicates());
Copy link
Member

@ST-DDT ST-DDT Jan 2, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing null checks.

* @param y The y coordinate counted from the top
* @return The ingredient predicate at this position defined by the aisle
*/
default Optional<Predicate<ItemStackSnapshot>> getIngredientPredicate(int x, int y) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are these methods default implemented?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So people who want to implement this interface themselves don't have to implement this method.

Copy link
Contributor

@Deamon5550 Deamon5550 Jan 2, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is that not what builders are for? if you're cluttering up the API to make it a little easier for the tiny percent of people why have to implement this in a plugin then you can remove all of these default implementations.

The only things that should be default implemented is when its a trivial helper method.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd find it incredibly useful to not have to write these myself, what is the issue exactly? All ShapedCraftingRecipes should behave like this, this is why they are default-implemented. And in corner cases, it also makes it possible to override them. It sounds like a win-win scenario to me. Note that all CraftingRecipes have an isValid method.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd much rather have the least amount of actual implementation in the API. That being said, this is implementation, not API.

* or you can simply use the {@link ShapelessCraftingRecipe.Builder} or {@link ShapedCraftingRecipe.Builder}.</p>
*/
public interface CraftingRecipe extends Recipe {
/**
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing new line after body start.

}

/**
* @return An unmodifiable copy of the original aisle
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs full javadocs.

int gapHeight = grid.getRows() - getHeight();

// Shift the aisle along the grid wherever possible
for(int offsetX = 0; offsetX <= gapWidth; offsetX++) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for ( (space)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do I have my checkstyle plugin set up incorrectly? I am not getting the warnings here.

@Maxqia
Copy link
Contributor

Maxqia commented Jan 3, 2017

-_- #1419

@Limeth
Copy link
Contributor Author

Limeth commented Jan 3, 2017

Began implementing the SpongeCommon part.

Copy link
Contributor

@Deamon5550 Deamon5550 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still some implementation in here, a few missing or misworded javadocs, and you need to go through with your IDE and fix the line wrapping of your javadocs its all over the place.

*/
boolean isValid(GridInventory grid);

/**
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing javadoc

import java.util.Optional;

/**
* <p>A CraftingRecipe represents some craftable recipe in the game.</p>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't need paragraph tags for the first paragraph.

* <p>The requirements of a CraftingRecipe can be general, they just have to
* eventually return a boolean given an itemgrid.</p>
*
* <p>You can implement it manually to suit your creative needs,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Skip this bit, its confusing and its stating the obvious.

* This method is preferred over the {@link #getExemplaryResult()} method,
* as it customizes the result further depending on the specified
* input {@param grid}.
* It is advised to use the output of {@link #getExemplaryResult()},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Either join this to the previous line or make it a new paragraph.

*
* @return An unmodifiable copy of the original aisle
*/
List<String> getAisle();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The aisle stuff is great for the builder but it doesn't have a place in the actual recipe. You already have width and height information and a method for getting the predicate for a position, keeping the aisle stuff around is useless as translating the characters is way more work than just using the methods that take an x, y position.

* @return The result of smelting the {@param ingredient} {@link ItemStack}, if the {@param ingredient} is valid
*/
default Optional<ItemStack> getResult(ItemStackSnapshot ingredient) {
Preconditions.checkNotNull(ingredient, "The ingredient must not be null");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove this implementation

import java.util.Optional;

/**
* A general interface for furnace recipes.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whatever you are wrapping the javadocs in this whole file to it isn't 80 characters.

*/
ItemStackSnapshot getExemplaryIngredient();

/**
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing javadoc

}

/**
* This method is preferred over the {@link #getExemplaryResult()} method,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't say preferred, this is the method to use.

}

/**
* @param ingredient The ingredient being smelted
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing javadoc

@Limeth
Copy link
Contributor Author

Limeth commented Jan 3, 2017

@Deamon5550 Thanks for the review. Should this be highlighted by the checkstyle plugin? I am not getting any warnings like those you named.

@Deamon5550
Copy link
Contributor

The checkstyle and formatter do their best to help you but common sense is always the best.

@Limeth
Copy link
Contributor Author

Limeth commented Jan 4, 2017

@Deamon5550 Fixed.

Copy link
Member

@ST-DDT ST-DDT left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice API design. I guess this will become quite handy.

/**
* Builds a simple furnace recipe
*/
interface Builder extends ResettableBuilder<SmeltingRecipe, Builder> {
Copy link
Member

@ST-DDT ST-DDT Jan 7, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Missing javadocs for all methods inside this builder.

* or {@link Optional#empty()} if the given
* {@link GridInventory} does not match this recipe's requirements.
*/
Optional<List<ItemStack>> getRemainingItems(GridInventory grid, World world);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I quite unsure about this one.

Wouldn't be a Optional<CraftingResult> better here?
Because currently you have to check the validity of the recipe twice (one for the result stack and one time for the remaining stacks).

If you use a CraftingResult which would store both of them at once you could skip one validity execution.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the suggestion. Originally, the methods used to have different parameters, I didn't notice they could be merged together after changing them, thanks for pointing this out!

@Limeth
Copy link
Contributor Author

Limeth commented Jan 8, 2017

CraftingRecipe#getResult(GridInventory grid, World world) and getRemainingItems(GridInventory grid, World world) cannot be combined, because there are separate methods calling them in net.minecraft.item.crafting.CraftingManager.
I had to make a choice whether to make these two API methods return Optional<T>, where Optional#empty() would be returned when the recipe is not fulfilled, or just T without checking the recipe requirements. The downside of returning T directly is, that the API relies on that the user has checked the isValid method; on the other hand, if they decide to call both API methods, they only have to check the validity once.
Minecraft actually checks the recipe validity for both methods, so I decided to go with the Optional<T> return type.

Edit: Since users would have to implement IRecipe, the methods must basically be the same.

*/
package org.spongepowered.api.item.recipe.smelting;

public interface SmeltingRecipeRegistry {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You forgot to extend the RecipeRegistry.

* @return The amount of experience released after completing this recipe,
* if the {@param ingredient} is valid
*/
Optional<Double> getExperience(ItemStackSnapshot ingredient);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use java.util.OptionalDouble

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But there are no map/flatMap functions for chaining in OptionalDouble. Is performance really at stake here?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you have 2 wrappers if you can just have one @Limeth?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exactly for the reason I stated above.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't really need to map for doubles.

Copy link
Member

@ST-DDT ST-DDT Jan 14, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

liach is referring to .map(Function<T,R>).

Well if the absent result result is equivalent to "does not grant experience" then it could just return primitive double zero

* @param result The resultant snapshot
* @return The builder
*/
Builder result(@Nullable ItemStackSnapshot result);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above.

*/
ShapedCraftingRecipe build();
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

formatting

return Sponge.getRegistry().createBuilder(Builder.class);
}


Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extra empty lines

@gabizou gabizou mentioned this pull request Jan 10, 2017
@me4502
Copy link
Contributor

me4502 commented Jan 12, 2017

This is more a question of curiosity, but does this support information such as Lore, Names, Enchantments, etc, on recipe items?

@Limeth
Copy link
Contributor Author

Limeth commented Jan 13, 2017

@me4502 Yes, you can implement that ingredient predicate yourself and only return true if correct lore is present. I'm currently stuck implementing the SpongeCommon part of this PR, so if you anyone would like to help, that would be awesome.

* is completed
* @return This builder, for chaining
*/
Builder experience(@Nullable Double experience);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does null stand for here? Use default?

* @return This builder, for chaining
*/
default Builder result(@Nullable ItemStack result) {
return result(result != null ? result.createSnapshot() : null);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like you missed this one by not using ItemStackSnapshot.NONE.

*/
Optional<List<ItemStack>> getResults(GridInventory grid);

ItemStackSnapshot getExemplaryResult();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add an empty line after the last member in a class/interface.

* @param aisle A string array of ingredients
* @return The builder
*/
Builder aisle(@Nullable String... aisle);
Copy link
Member

@ST-DDT ST-DDT Jan 14, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • This method should mention what happens to the previously registered ingredient Predicates. (Especially those, that are no loner needed for this aisle.)

Ref: where(char, Predicate) <- @throws IllegalArgumentException If the aisle does not contain the specified character symbol

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should clarify what an 'asile' means here. If I'm correct in my understanding of it, each 'asile' corresponds to a row of the crafting grid.

* @throws IllegalArgumentException If the aisle does not contain
* the specified character symbol
*/
Builder where(char symbol, ItemStackSnapshot ingredient) throws IllegalArgumentException;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I recommend adding @Nullable here to keep the remove if null part. This should also document what happens if the ItemStackSnapshot.NONE is set (IMO its the same as null => remove ingredient )

*/
@SuppressWarnings("ConstantConditions")
default Builder result(@Nullable ItemStack result) {
return result(result != null ? result.createSnapshot() : ItemStackSnapshot.NONE);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method should document the implications of this ItemStackSnapshot.NONE here.
IMO an empty stack is not a valid result.

*/
@SuppressWarnings("ConstantConditions")
default Builder where(char symbol, @Nullable ItemStack ingredient) throws IllegalArgumentException {
return where(symbol, ingredient != null ? ingredient.createSnapshot() : ItemStackSnapshot.NONE);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not quite sure about the NONE part. because its effectifly an invalid ingredient.
Otherwise you have to document the implications of using this. Especially since the implications are slightly different than the other methods.

* method, as it customizes the result further depending on the specified
* {@param ingredient} {@link ItemStackSnapshot}. It is advised to use
* the output of {@link #getExemplaryResult()}, modify it accordingly,
* and {@code return} it.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure whether the main javadoc part should be about the implementation. I would recommend explaining when it is used.

Out of thin air example:

This method is called at the beginning of the smelting process to check whether there is space 
for the result and at the end of the smelting to calculate to actually add it to the results.

<p>Note: It is advised that the result from getExemplaryResult and this method are strictly 
related to each other. 
/ ... the results from this method are a derived result from getExemplaryResult </p>

* @param result The output of this recipe
* @return This builder, for chaining
*/
Builder result(@Nullable ItemStackSnapshot result);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What are the implications of setting null here?
IMO setting it to null is useless since you have to set it to something none null anyway.

* @param ingredient The required ingredient
* @return This builder, for chaining
*/
Builder ingredient(@Nullable ItemStackSnapshot ingredient);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Document null behaviour. See above.

* @param ingredient The required ingredient
* @return This builder, for chaining
*/
default Builder ingredient(@Nullable ItemStack ingredient) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Document null behaviour. See above.

*/
SmeltingRecipe build();
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing newline + one a line above

* Builds a simple furnace recipe
*/
interface Builder extends ResettableBuilder<SmeltingRecipe, Builder> {
/**
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing newline

Copy link
Member

@ST-DDT ST-DDT left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good job. Almost done.

*
* <p>It is advised to use the output of
* {@link CraftingRecipe#getExemplaryResult()}, modify it accordingly,
* and {@code return} it.</p>
Copy link
Member

@ST-DDT ST-DDT Jan 14, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There should be a hint that this comment is only for implementing classes.

Implementing classes are advised...optionally cache it and {@code return} it.

* @return The builder
*/
default Builder result(ItemStack result) {
return result(result.createSnapshot());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There should be a null check here checkNotNull(result, "result")

* @return This builder, for chaining
*/
default Builder result(ItemStack result) {
return result(result.createSnapshot());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing null check
return result(checkNotNull(result, "result").createSnapshot());

Copy link
Contributor

@Maxqia Maxqia left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ehhhhhh

* @return {@code width * height} for shaped recipes, or the number of
* ingredients for shapeless recipes
*/
int getSize();
Copy link
Contributor

@Maxqia Maxqia Jan 15, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Invalid Description, It wouldn't do that if you added it after. Honestly this function isn't really necessary at all.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See above.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe the order defined in the implementation is so that the crafting manager can prefer large recipes (3x3) before considering smaller 2x2 ones.
This is somewhat implementation detail that need not be exposed.
For shaped recipes the implementation just needs to do getWidth() * getHeight() and for shapeless do getIngredientPredicates().size()

* @param world The world this recipe would be used in
* @return True if the given input matches this recipe's requirements
*/
boolean isValid(GridInventory grid, World world);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd say you should just combine this with getResult and default implement it to return false if it returns ItemTypes.AIR or null...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In order to support listing modded recipes, I had to make CraftingRecipe symmetric to IRecipe. Making this return the ItemStackSnapshot would also unnecessarily create instances in cases they aren't needed.

* @param ingredient The ingredient to check against
* @return Whether this {@param ingredient} can be used to craft the result
*/
boolean isValid(ItemStackSnapshot ingredient);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicate Function? and see above...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a duplicate. Extends Recipe, not CraftingRecipe.


import org.spongepowered.api.item.inventory.ItemStackSnapshot;

public interface SmeltingResult {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this a separate interface?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this case, we can afford to do the optimization you suggested for CraftingRecipe#isValid(GridInventory, World), because there is no special interface for smelting recipes in Minecraft code. This way, users don't get access to the resulting ItemStackSnapshot and the amount of experience released, unless the SmeltingRecipe#isValid(ItemStackSnapshot) returns true.

* Checks if the given {@link GridInventory} fits the required constraints
* to craft this {@link CraftingRecipe}.
*
* @param grid The ItemGrid to check for validity
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ItemGrid was an old name, should now be GridInventory

* @param grid The crafting input, typically 3x3 or 2x2
* @return An {@link ItemStackSnapshot}
*/
ItemStackSnapshot getResult(GridInventory grid);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Recipes can output multiple items
See this comment: #242 (comment)

Also, it's not defined what would happen if isValid returns false, does it throw an exception? Maybe it should return an Optional?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Crafting recipes cannot. That comment talks about pulverizer recipes.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens is undefined behavior, no way around that.


/**
* This method should only be called if
* {@link #isValid(GridInventory, World)} returns {@code true}.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, need to define what happens if isValid returns false

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See above.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

its not undefined it should throw an exception

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, yes, I could add a line that the method might throw a specific exception, but it does not necessarily check the validity of the input crafting grid.

* @return The result of fulfilling the requirements of a
* {@link SmeltingRecipe}
*/
ItemStackSnapshot getResult();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should be a list of items. Modded recipes may return more than one item (e.g. a furnace that can smelt double at once)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but only in "modern" GUIs. Those recipes are not supported for standard vanilla GUIs as there is only one output slot.

private final double experience;

@SuppressWarnings("ConstantConditions")
public SmeltingResult(@Nonnull ItemStackSnapshot result, double experience) {
Copy link
Contributor

@Deamon5550 Deamon5550 Jan 16, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Nonnull is not needed as things are nonnull by default

@Limeth Limeth force-pushed the feature/recipe-ii branch from d29c46e to ea6161b Compare February 8, 2017 14:29
* @return The exemplary result of this recipe
*/
Optional<List<ItemStack>> getResults(GridInventory grid);
ItemStackSnapshot getExemplaryResult();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if 'exemplary' makes sense here. What does this represent?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think its the item you see in the CraftingOutput before you take it out.

Copy link
Contributor Author

@Limeth Limeth Feb 10, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The native IRecipe class contains an ItemStack getRecipeOutput(), where this value is used as the return value, and the FurnaceRecipes class contains a Map<ItemStack, ItemStack> smeltingList, where this value is used as a value in the map. I think these might be used in TooManyItems/NotEnoughItems to display the recipes, that's why I chose the word 'exemplary'.

* @throws IllegalArgumentException If the aisle does not contain
* the specified character symbol
*/
Builder where(char symbol, @Nullable ItemStackSnapshot ingredient) throws IllegalArgumentException;
Copy link
Contributor

@Aaron1011 Aaron1011 Feb 10, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Limeth: That link seems to be broken. Can the implementation take care of passing in ItemStackSnapshot.NONE to the predicate, if that's what the issue is?

}

/**
* Creates a predicate with vanilla matching behavior.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you clarify what this means? Otherwise, plugins may not find it very useful.

* @param aisle A string array of ingredients
* @return The builder
*/
Builder aisle(@Nullable String... aisle);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should clarify what an 'asile' means here. If I'm correct in my understanding of it, each 'asile' corresponds to a row of the crafting grid.

@Limeth
Copy link
Contributor Author

Limeth commented Feb 10, 2017

@Aaron1011: You said, that That link seems to be broken. Can the implementation take care of passing in ItemStackSnapshot.NONE to the predicate, if that's what the issue is?
I couldn't reply directly to that comment for some reason. Anyway, I don't quite understand what you're pointing out here, could you elaborate?

@Aaron1011
Copy link
Contributor

@Limeth: What I'm wondering is this: Why do we need the overload of where that takes an ItemStackSnapshot (i.e. where(char symbol, @Nullable ItemStackSnapshot ingredient)). Is there something that can't be accomplished through passing in a Predicate?

@Limeth
Copy link
Contributor Author

Limeth commented Feb 10, 2017

@Aaron1011 No, this is just a convenience method which uses the getVanillaIngredientPredicate you pointed out above. We don't need to overload it, but I'd find it useful.


@Override
public int hashCode() {
int result1 = this.result.hashCode();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can use Objects.hash(this.result, this.remainingItems) here.

@parlough
Copy link
Contributor

parlough commented Mar 24, 2017

It seems like recipes and similar will change heavily in 1.12 with some changes Dinnerbone is working on, so it may be best to revisit this with that update.

@gabizou
Copy link
Member

gabizou commented Mar 24, 2017

It seems like recipes and similar will change heavily in 1.12(or maybe 1.13) with some changes Dinnerbone is working on, so it may be best to revisit this with that update.

I'd rather have recipes partially implemented for API 6 and 7 while we still can, if not fully implemented, so that when MC 1.12 comes around, we can easily update the API as necessary, along with the implementation.

@Faithcaio
Copy link
Contributor

MC 1.12 is out and Sponge still has no working Recipe API.
What is holding this back?

@Zidane
Copy link
Member

Zidane commented Jun 12, 2017

@Faithcaio @gabizou Not really interested in adding work back for older versions. Now that Forge 1.12 is in the wild, mods will skip 1.11 and head straight for that and leave 1.10 behind. This API needs to be updated for 1.12's changes and proceed from there.

@parlough
Copy link
Contributor

parlough commented Jun 12, 2017

It seems he has a few changes to make here still but especially in the implementation with there being suggestions, questions, merge conflicts, and I'm assuming issues with 1.12 changes. @Limeth do you have the time and/or interest to continue these pull requests?

@Limeth
Copy link
Contributor Author

Limeth commented Jun 12, 2017

I got discouraged by the PR process and am no longer interested in contributing. If anyone wants to take over this PR, feel free to do so.

@liach
Copy link
Contributor

liach commented Jun 12, 2017

Yeah Sponge devs often respond slowly.

@kashike
Copy link
Contributor

kashike commented Jun 12, 2017

@Limeth May I ask what discouraged you, and why you're no longer interested in contributing?

@Limeth
Copy link
Contributor Author

Limeth commented Jun 12, 2017

@kashike It was difficult for me to adapt to the required code style, most of the responses on my PRs were only addressing the "distracting" parts of code that didn't follow the code style. This lead to much longer periods of the iterative development process (code/compile/test/debug), as I felt like I didn't get enough feedback on the actual logic I was implementing.
Along with this, I got busy with school and when I returned, I realized programming Minecraft plugins wasn't what I'd like to continue on doing, at least for now.

@jamierocks
Copy link
Contributor

As it has been brought up, I thought I would share https://dev.to/terrehbyte/my-pull-request-etiquette - I thought it was a rather good read, and although not all of it would be easy for Sponge to implement, I'm sure it'd be worth some though ;)

There are a number of other good reads over on dev.to regarding code reviews, such as: https://dev.to/vaidehijoshi/crafting-better-code-reviews and https://dev.to/cyberomin/a-pull-request-is-submitted-what-next

@Faithcaio Faithcaio mentioned this pull request Jun 15, 2017
11 tasks
@kashike
Copy link
Contributor

kashike commented Jun 15, 2017

Superseded by #1582

@kashike kashike closed this Jun 15, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

api: 6 (u) version: 1.11 (unsupported since Jan 1st 2018) status: wip system: inventory

Projects

None yet

Development

Successfully merging this pull request may close these issues.